a tool for shared writing and social publishing
1import { NextResponse } from "next/server"; 2import { DidResolver } from "@atproto/identity"; 3import { parseReqNsid, verifyJwt } from "@atproto/xrpc-server"; 4import { supabaseServerClient } from "supabase/serverClient"; 5import { PubLeafletDocument } from "lexicons/api"; 6 7const serviceDid = "did:web:leaflet.pub:lish:feeds"; 8export async function GET( 9 req: Request, 10 { params }: { params: Promise<{ path: string[] }> }, 11) { 12 let { path } = await params; 13 if (path[0] === "did.json") 14 return NextResponse.json({ 15 "@context": ["https://www.w3.org/ns/did/v1"], 16 id: serviceDid, 17 service: [ 18 { 19 id: "#bsky_fg", 20 type: "BskyFeedGenerator", 21 serviceEndpoint: `https://leaflet.pub/lish/feeds`, 22 }, 23 ], 24 }); 25 let auth = validateAuth(req, serviceDid); 26 if (!auth) return NextResponse.json({}, { status: 301 }); 27 let { data: publications } = await supabaseServerClient 28 .from("publication_subscriptions") 29 .select(`publications(documents_in_publications(documents(*)))`) 30 .eq("identity", auth); 31 return NextResponse.json({ 32 feed: [ 33 ...(publications || []).flatMap((pub) => { 34 let posts = pub.publications?.documents_in_publications || []; 35 return posts.flatMap((p) => { 36 if (!p.documents?.data) return []; 37 let record = p.documents.data as PubLeafletDocument.Record; 38 if (!record.postRef) return []; 39 return { post: record.postRef.uri }; 40 }); 41 }), 42 ], 43 }); 44} 45 46const didResolver = new DidResolver({}); 47const validateAuth = async ( 48 req: Request, 49 serviceDid: string, 50): Promise<string | null> => { 51 const authorization = req.headers.get("authorization"); 52 if (!authorization?.startsWith("Bearer ")) { 53 return null; 54 } 55 const jwt = authorization.replace("Bearer ", "").trim(); 56 const nsid = parseReqNsid(req); 57 const parsed = await verifyJwt(jwt, serviceDid, nsid, async (did: string) => { 58 return didResolver.resolveAtprotoKey(did); 59 }); 60 return parsed.iss; 61};